import struct
import threading

import serial
# if run on the linux, use it
# from serial.serialposix import Serial
from serial.serialutil import EIGHTBITS, PARITY_NONE, STOPBITS_ONE
from serial.serialwin32 import Serial


class SerialConnector:
    _instance = None
    _instance_lock = threading.Lock()

    @classmethod
    def get_instance(cls):
        if not cls._instance:
            with cls._instance_lock:
                if not cls._instance:
                    cls._instance = SerialConnector()
        return cls._instance

    def __init__(self):
        self.serial_client: Serial = None

    def serial_connect(self, port: str = "COM3", baud_rate=19200, bytesize=EIGHTBITS, parity=PARITY_NONE,
                       stopbits=STOPBITS_ONE,
                       timeout=None):
        if not self.serial_client:
            try:
                self.serial_client = serial.Serial(port=port, baudrate=baud_rate, bytesize=bytesize, parity=parity,
                                                   stopbits=stopbits, timeout=timeout)
                print(f"serial connected: {self.serial_client}")
            except serial.SerialException as e:
                print(f"serial not connected: {e}")
        else:
            print(f"serial already connected: {self.serial_client}")

    def serial_open(self):
        if not self.serial_client.is_open:
            self.serial_client.open()
        else:
            print(f"serial already open: {self.serial_client}")

    def serial_close(self):
        self.serial_client.close()

    def write_bytes(self, data) -> int:
        """
        wirtes bytes to register
        :param data:
        :return:
        """
        res = self.serial_client.write(bytes(data, "utf-8"))
        print(f"serial write bytes result is: {res}")
        return res

    def read_bytes(self) -> str:
        """
        reads bytes from register
        :return:
        """
        data_bytes = self.serial_client.readline()
        return str(data_bytes, "utf-8")

    def calcCRC_16(self, command):
        """
        calculates CRC16 checksum
        :param command:
        :return:
        """
        crc = 0xFFFF
        for pos in command:
            crc ^= pos
            for i in range(8):
                if ((crc & 1) != 0):
                    crc >>= 1
                    crc ^= 0xA001
                else:
                    crc >>= 1
        # return hex(((crc & 0xff) << 8) + (crc >> 8))

        ret = struct.pack("i", ((crc & 0xff) << 8) + (crc >> 8))
        return [ret[0], ret[1]]


if __name__ == "__main__":
    device_serial = SerialConnector.get_instance()
    device_serial.serial_connect()
    device_serial.serial_open()
    device_serial.write_bytes("01 06 00 3F 00 07 F8 04")
